﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.OleDb;
using System.Threading;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Text;
using Aandelendelen.Db;
using NLog;

namespace Aandelendelen.Mail
{
	public class MailThread
	{
		private static Logger logger = LogManager.GetCurrentClassLogger();

		private const int maxMailToHandleAtOnce = 32;

		Collection<EMail> mails = new Collection<EMail>();
		bool isRunning = false;
		PostScriptumCollection postScriptums = PostScriptumCollection.GetInstance();

		public MailThread()
		{
			this.Start();
		}

		public void Signal()
		{
			this.Start();
		}

		private void Start()
		{
			if (Global.IsMailBlocked)
				return;

			if (this.isRunning == false)
			{
				this.isRunning = true;
				ThreadPool.QueueUserWorkItem(new WaitCallback(this.Poll));
			}
			else
			{
				logger.Warn("Attempt to start mailthread while already running");
			}
		}

		private void Poll(object stateInfo)
		{
			logger.Info("Mailthread started");

			this.GetMailsWaiting();

			int count = this.mails.Count;

			while (count > 0)
			{
				if (this.DoMail() == false) //no mails sent successfully? Break out.
				{
					logger.Error("No mails sent!");
					break;
				}

				this.GetMailsWaiting();

				count = this.mails.Count;
			}

			logger.Info("Mailthread finished");

			this.Finish();
		}

		private void Finish()
		{
			this.isRunning = false;
		}

		private void SetMailStatusToSent(Collection<MailSent> sentMails)
		{
			if (sentMails.Count == 0)
				return;
			/*
			StringBuilder str = new StringBuilder("UPDATE Mail SET DateSent = Now() WHERE Id IN (", 256);
			for (int i=0; i<ids.Count; i++)
			{
				int id = ids[i];
				if (i > 0)
					str.Append(",");
				str.Append(id);
			}
			str.Append(")");
			*/
			OleDbConnection connection = this.OpenMailConnection();
			OleDbTransaction transaction = connection.BeginTransaction();

			foreach (MailSent mailSent in sentMails)
			{
				OleDbCommand command = new OleDbCommand("SetStatusToSent", connection, transaction);
				command.CommandType = CommandType.StoredProcedure;

				OleDbParameter parId = new OleDbParameter("id", OleDbType.Integer);
				parId.Value = mailSent.DbId;
				OleDbParameter parDateSent = new OleDbParameter("datesent", OleDbType.Date);
				parDateSent.Value = mailSent.Sent;

				command.Parameters.Add(parDateSent);
				command.Parameters.Add(parId);

				command.ExecuteScalar();
			}

			transaction.Commit();

			connection.Close();
		}

		private bool DoMail()
		{
			SmtpClient smtpClient = new SmtpClient();

			bool retVal = false;

			Collection<MailSent> sentMails = new Collection<MailSent>();

			foreach (EMail mail in this.mails)
			{
				try
				{
					string from = "info@aandelendelen.nl";
					string sendBody = mail.SendBody;

					if (mail.SendBody.StartsWith("{from:"))
					{
						int index = mail.SendBody.IndexOf("}", 1);
						if (index > -1)
						{
							from = mail.SendBody.Substring(6, index - 6);
							sendBody = mail.SendBody.Substring(index + 1);
						}
					}
					MailMessage msg = new MailMessage(
											new MailAddress(from, "AandelenDelen.nl"),
											new MailAddress(mail.Recipients)
											);

					msg.Subject = mail.Subject;
					msg.Body = sendBody;
					msg.SubjectEncoding = System.Text.Encoding.UTF8;
					msg.BodyEncoding = System.Text.Encoding.UTF8;
					msg.IsBodyHtml = true;

					if (mail.Bcc)
						msg.Bcc.Add("info@aandelendelen.nl");

					if (mail.AttachmentPath != null)
					{
						Attachment data = new Attachment(mail.AttachmentPath, MediaTypeNames.Application.Octet);
						// Add time stamp information for the file.
						ContentDisposition disposition = data.ContentDisposition;
						disposition.CreationDate = System.IO.File.GetCreationTime(mail.AttachmentPath);
						disposition.ModificationDate = System.IO.File.GetLastWriteTime(mail.AttachmentPath);
						disposition.ReadDate = System.IO.File.GetLastAccessTime(mail.AttachmentPath);

						msg.Attachments.Add(data);
					}

					try
					{
						if (Global.IsProductionEnvironment)
							smtpClient.Send(msg);
						else
							//simulate send
							Thread.Sleep(100);

						sentMails.Add(new MailSent(mail.DBId));

						retVal = true; //at least one mail was sent successfully, so return true to continue immediately with next 32 mails.
					}
					catch (Exception e)
					{
						//if Send fails, leave the msg in the db. We will try again automatically in a while.
						logger.ErrorException("Error while sending mail to " + mail.Recipients, e);
					}
				}
				catch
				{
					//no action on general failure, like incorrect email or something.
				}
			}

			this.SetMailStatusToSent(sentMails);

			return retVal;
		}

		private void GetMailsWaiting()
		{
			OleDbConnection connection = this.OpenMailConnection();

			OleDbCommand countCommand = new OleDbCommand("SELECT COUNT(Id) FROM Mail WHERE DateSent = #1/1/1980#", connection);
			int count = (int)countCommand.ExecuteScalar();
			logger.Info("{0} mails in queue.", count);

			OleDbCommand selectCommand = new OleDbCommand("SELECT TOP " + MailThread.maxMailToHandleAtOnce.ToString() + " m.Id, m.Recipient, m.Bcc, t.Subject, t.Txt, m.Attachment, m.PsId FROM Mail m, Mailtext t WHERE m.DateSent = #1/1/1980# AND m.TextId = t.Id", connection);

			OleDbDataReader reader = selectCommand.ExecuteReader();

			this.mails.Clear();

			object[] row = new object[7];

			while (reader.Read())
			{
				reader.GetValues(row);

				int psID = (int)row[6];
				StringBuilder str = new StringBuilder((string)row[4]);
				if (psID > 0)
				{
					PostScriptum ps = this.postScriptums.FindID(psID);
					if (ps != null)
					{
						str.Append("<br><br>");
						str.Append(ps.Text);
					}
				}

				EMail mail = new EMail((int)row[0], (string)row[1], (bool)row[2], (string)row[3], str.ToString());
				if (false == (row[5] is DBNull || row[5] == null))
				{
					mail.AttachmentPath = (string)row[5];
				}

				this.mails.Add(mail);
			}

			reader.Close();

			connection.Close();
		}

		private OleDbConnection OpenMailConnection()
		{
			OleDbConnection connection = new OleDbConnection(DbUtils.GetConnectionString(AandelenDelenDb.AandelenDelenMail));

			connection.Open();

			return connection;
		}

		private class MailSent
		{
			public MailSent(int dbId)
			{
				this.DbId = dbId;
				this.Sent = DateTime.Now;
			}

			public int DbId
			{
				get;
				private set;
			}

			public DateTime Sent
			{
				get;
				private set;
			}
		}

	}
}